package org.github.manx98.jlibhac.common.math;

import cn.hutool.core.util.ByteUtil;
import lombok.extern.slf4j.Slf4j;
import net.jpountz.lz4.LZ4Factory;
import org.github.manx98.jlibhac.common.reflect.StructureMap;
import org.github.manx98.jlibhac.fs.IStorage;
import org.github.manx98.jlibhac.fs.MemoryStorage;

import java.io.IOException;
import java.lang.reflect.*;
import java.nio.ByteOrder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;

/**
 * @author Manx98
 * @version 1.0
 * @since 2022/10/2 21:20
 */
@Slf4j
public class IByteUtil extends ByteUtil {

    /**
     * 切割字节数组
     */
    public static byte[] slice(byte[] target, int offset, int length) {
        byte[] bytes = new byte[length];
        System.arraycopy(target, offset, bytes, 0, length);
        return bytes;
    }

    /**
     * 切割数组并反转
     */
    public static byte[] reverseSlice(byte[] val, int off, int len) {
        byte[] bytes = new byte[len];
        for (int i = 0; i < len; i++) {
            bytes[len - i - 1] = val[off + i];
        }
        return bytes;
    }

    public static <T> void mapToStruct(T target, byte[] data, int offset) {
        mapToStruct(target, data, offset, data.length - offset);
    }

    /**
     * 利用反射自动填充对象属性
     *
     * @param target  对象
     * @param storage 数据
     * @return 已映射的内存大小
     */
    public static int mapToStruct(Object target, IStorage storage) {
        int memorySize = 0;
        Class<?> type = target.getClass();
        for (StructureField structureField : getDeclaredFields(type)) {
            try {
                Class<?> fieldType = structureField.field.getType();
                if (structureField.offset < 0) {
                    storage.setPosition(memorySize);
                } else {
                    storage.setPosition(structureField.offset);
                }
                MappedResult mappedResult = null;
                if (fieldType.isEnum()) {
                    mappedResult = convertToEnum(fieldType, storage);
                } else if (fieldType.isArray()) {
                    mappedResult = mapToArray(fieldType, storage, structureField.length);
                } else {
                    mappedResult = mapType(fieldType, storage, structureField.length);
                }
                memorySize += mappedResult.memorySize;
                structureField.field.set(target, mappedResult.value);
                log.debug("[offset={}]设置 {} 属性[{}]值为: {}", storage.getPosition(), type.getSimpleName(), structureField.field.getName(), mappedResult.value);
            } catch (IllegalAccessException | IOException | InvocationTargetException | NoSuchMethodException |
                     InstantiationException e) {
                throw new RuntimeException(e);
            }
        }
        log.debug("成功填充对象: {}", target);
        return memorySize;
    }

    public static int binarySearchLong(byte[] offsets, long virtualAddress) {
        for (int i = 0; i < offsets.length / 8; i++) {
            if (bytesToLong(offsets, i * 8, ByteOrder.LITTLE_ENDIAN) == virtualAddress) {
                return i;
            }
        }
        return -1;
    }

    public static byte[] sliceLong(byte[] buffer, int offsetLongNum) {
        offsetLongNum *= 8;
        byte[] bytes = new byte[buffer.length - offsetLongNum];
        System.arraycopy(buffer, offsetLongNum, bytes, 0, buffer.length - offsetLongNum);
        return bytes;
    }

    public static byte[] sliceLong(byte[] buffer, int offsetLongNum, int count) {
        offsetLongNum *= 8;
        byte[] bytes = new byte[count * 8];
        System.arraycopy(buffer, offsetLongNum, bytes, 0, bytes.length);
        return bytes;
    }

    public static void Lz4Decompress(byte[] encBuffer, byte[] decBuffer) {
        LZ4Factory factory = LZ4Factory.fastestInstance();
        factory.fastDecompressor().decompress(encBuffer, decBuffer);
    }

    static class StructureField implements Comparable<StructureField> {
        Field field;
        long offset;
        int length;

        public StructureField(Field field, long offset, int length) {
            this.field = field;
            this.offset = offset;
            this.length = length;
        }

        @Override
        public int compareTo(StructureField o) {
            return (int) (o.offset - offset);
        }

        @Override
        public String toString() {
            return "StructureField{" +
                    "field=" + field +
                    ", offset=" + offset +
                    ", length=" + length +
                    '}';
        }
    }

    private final static Map<String, StructureField[]> cacheStructureFields = new ConcurrentHashMap<>();

    public static StructureField[] getDeclaredFields(Class<?> target) {
        PriorityQueue<StructureField> fieldPriorityQueue;
        StructureField[] result = cacheStructureFields.get(target.getTypeName());
        if (result == null) {
            fieldPriorityQueue = new PriorityQueue<>();
        } else {
            return result;
        }
        Field[] declaredFields = target.getDeclaredFields();
        boolean foundZero = false;
        boolean foundNegativeNumber = false;
        boolean foundPositiveNumber = false;
        HashSet<Long> negativeNumber = new HashSet<>();
        for (Field field : declaredFields) {
            StructureMap annotation = field.getAnnotation(StructureMap.class);
            if (annotation != null) {
                long offset = annotation.offset();
                if (offset == 0) {
                    foundZero = true;
                } else if (offset < 0) {
                    if (negativeNumber.contains(offset)) {
                        throw new IllegalArgumentException("found many same negative number in annotation!");
                    } else {
                        negativeNumber.add(offset);
                    }
                    foundNegativeNumber = true;
                } else {
                    foundPositiveNumber = true;
                }
                fieldPriorityQueue.add(new StructureField(field, offset, annotation.length()));
            }
        }
        if (foundNegativeNumber && (foundPositiveNumber || !foundZero)) {
            throw new IllegalArgumentException("不是合法的顺序内存映射！");
        }
        result = fieldPriorityQueue.toArray(new StructureField[]{});
        cacheStructureFields.put(target.getTypeName(), result);
        return result;
    }

    public static <T> void mapToStruct(T target, byte[] data, int offset, int length) {
        mapToStruct(target, new MemoryStorage(data, offset, length));
    }

    public static enum MappedType {
        ARRAY,
        BYTE,
        INT,
        SHORT,
        LONG,
        OBJECT
    }

    public static class MappedResult {
        public final Object value;
        public final int memorySize;
        public final MappedType type;

        public MappedResult(Object value, int memorySize, MappedType type) {
            this.value = value;
            this.memorySize = memorySize;
            this.type = type;
        }
    }

    public static MappedResult mapToArray(Class<?> type, IStorage storage, int length) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        if (type.getTypeName().equals("byte[]")) {
            return mapType(type, storage, length);
        }
        int memorySize = 0;
        Class<?> type1 = type.getComponentType();
        Object[] result = (Object[]) Array.newInstance(type1, length);
        for (int i = 0; i < length; i++) {
            storage.setPosition(memorySize);
            MappedResult mappedResult = mapType(type1, storage, length);
            memorySize += mappedResult.memorySize;
            result[i] = mappedResult.value;
        }
        return new MappedResult(result, memorySize, MappedType.ARRAY);
    }

    public static MappedResult mapType(Class<?> type, IStorage storage, int length) throws IOException {
        switch (type.getTypeName()) {
            case "byte[]":
                return new MappedResult(storage.readBytes(length), length, MappedType.ARRAY);
            case "byte":
                return new MappedResult(storage.readByte(), 1, MappedType.BYTE);
            case "short":
                return new MappedResult(storage.readShort(), 2, MappedType.SHORT);
            case "int":
                return new MappedResult(storage.readInt(), 4, MappedType.INT);
            case "long":
                return new MappedResult(storage.readLong(), 8, MappedType.LONG);
            case "org.github.manx98.jlibhac.common.math.Uint16":
                return new MappedResult(storage.readUint16(), 2, MappedType.OBJECT);
            case "org.github.manx98.jlibhac.common.math.Uint32":
                return new MappedResult(storage.readUint32(), 4, MappedType.OBJECT);
            case "org.github.manx98.jlibhac.common.math.Uint64":
                return new MappedResult(storage.readUint64(), 8, MappedType.OBJECT);
            case "java.lang.String":
                return new MappedResult(storage.readUtf8Z(length), length, MappedType.OBJECT);
            default:
                try {
                    Constructor<?> constructor = type.getConstructor();
                    constructor.setAccessible(true);
                    Object o = constructor.newInstance();
                    return new MappedResult(o, mapToStruct(o, storage.slice(storage.getPosition())), MappedType.OBJECT);
                } catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
                         IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
        }
    }

    public static MappedResult convertToEnum(Class<?> clazz, IStorage storage) throws InvocationTargetException, IllegalAccessException, IOException {
        for (Method declaredMethod : clazz.getDeclaredMethods()) {
            if (declaredMethod.getName().equals("valueOf")) {
                Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                if (parameterTypes.length == 1 && parameterTypes[0].isPrimitive()) {
                    MappedResult mappedResult = mapType(parameterTypes[0], storage, -1);
                    return new MappedResult(declaredMethod.invoke(null, mappedResult.value), mappedResult.memorySize, MappedType.OBJECT);
                }
            }
        }
        throw new UnsupportedOperationException("enum " + " is not supported, it is must be implemented valueOf !" + clazz.getTypeName());
    }

    /**
     * 计算类型空间占用
     *
     * @param clazz 类型
     * @return 空间字节数
     */
    public static int sizeOf(Class<?> clazz) {
        int count = 0;
        for (Field declaredField : clazz.getDeclaredFields()) {
            Class<?> type = declaredField.getType();
            String typeName = null;
            if (type.isEnum()) {
                for (Method declaredMethod : type.getDeclaredMethods()) {
                    if (declaredMethod.getName().equals("valueOf")) {
                        Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                        if (parameterTypes.length == 1 && parameterTypes[0].isPrimitive()) {
                            typeName = parameterTypes[0].getTypeName();
                            break;
                        }
                    }
                }
                if (typeName == null) {
                    throw new IllegalArgumentException("不能计算枚举类型大小：" + type.getTypeName());
                }
            } else if (type.isArray()) {
                StructureMap annotation = type.getAnnotation(StructureMap.class);
                if (annotation != null) {
                    return annotation.length() * sizeOf(type.getComponentType());
                }
                return 0;
            } else {
                typeName = type.getTypeName();
            }
            switch (typeName) {
                case "byte":
                    count += 1;
                    break;
                case "short":
                    count += 2;
                    break;
                case "int":
                    count += 4;
                    break;
                case "long":
                    count += 8;
                    break;
                default:
                    count += sizeOf(type);
            }
        }
        return count;
    }
}
